/**
 * 
 */
package com.netflexitysolutions.amazonws.sdb.orm;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;

import com.netflexitysolutions.amazonws.sdb.SimpleDB;
import com.netflexitysolutions.amazonws.sdb.orm.cache.Level2Cache;
import com.netflexitysolutions.amazonws.sdb.orm.cache.Level2CacheFactory;
import com.netflexitysolutions.amazonws.sdb.orm.cache.NoopLevel2CacheFactory;
import com.netflexitysolutions.amazonws.sdb.orm.internal.CacheSessionListener;
import com.netflexitysolutions.amazonws.sdb.orm.internal.SimpleDBSessionImpl;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.SimpleDBMapping;

/**
 * @author netflexity
 *
 */
public class SimpleDBSessionFactoryBean implements InitializingBean, FactoryBean, SimpleDBSessionFactory{
	
	private static final int DEFAULT_EXECUTOR_THREADS = 100;
    
	private Resource mappingResource;
	private	Resource[] mappingResources;
	private Resource mappingRulesResource;
	private Properties properties;
	private Level2CacheFactory<?> cacheFactory = new NoopLevel2CacheFactory();
	
	private SimpleDBMapping mapping;
	private SimpleDB dataSource;
	private SimpleDBSession session;
	private List<SimpleDBSessionListener> entityInterceptors = new ArrayList<SimpleDBSessionListener>();
	private int maxResults = 250;
	
	private int numOfExecutorThreads = DEFAULT_EXECUTOR_THREADS;
	private ExecutorService queryExecutor;
	private ExecutorService manipulationExecutor;
	
	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#getObject()
	 */
	public Object getObject() throws Exception {
		return session;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
	 */
	public Class<SimpleDBSession> getObjectType() {
		return SimpleDBSession.class;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#isSingleton()
	 */
	public boolean isSingleton() {
		return true;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		assert(mappingResource != null || (mappingResources != null && mappingResources.length > 0));

		// Initialize executors.
		int queryExecutorThreads = new Double(.75D * getNumOfExecutorThreads()).intValue();
		queryExecutor = Executors.newFixedThreadPool(queryExecutorThreads);
		manipulationExecutor = Executors.newFixedThreadPool(getNumOfExecutorThreads() - queryExecutorThreads);
		
		// Digest mapping.
		Digester digester = DigesterLoader.createDigester(mappingRulesResource.getURL());
		if (mappingResource != null && mappingResources != null) {
			throw new IllegalArgumentException("Either mappingResource or mappingResources should be set but not both");
		}
		if (mappingResource == null && mappingResources == null) {
			throw new IllegalArgumentException("Either mappingResource or mappingResources should be set");
		}
		if (mappingResource != null) {
			mapping = (SimpleDBMapping)digester.parse(mappingResource.getInputStream());
		} else {
			if (mappingResources.length == 0) {
				throw new IllegalArgumentException("mappingResources are empty");
			}
			mapping = (SimpleDBMapping)digester.parse(mappingResources[0].getInputStream());
			for (int i = 1; i < mappingResources.length; i++) {
				SimpleDBMapping tempMapping = (SimpleDBMapping)digester.parse(mappingResources[i].getInputStream());
				Map<String, String> queries = tempMapping.getQueriesMap();
				for (Entry<String, String> queryEntry : queries.entrySet()) {
					mapping.addQuery(queryEntry.getKey(), queryEntry.getValue());
				}
				List<ItemMetadata> items = tempMapping.getItemMetadata();
				for (ItemMetadata itemMetadata : items) {
					mapping.addItemMetadata(itemMetadata);
				}
			}
		}
		
		// Create session.
		session = new SimpleDBSessionImpl(this);
		
		// Add cachable listener.
		entityInterceptors.add(new CacheSessionListener(session));
	}
	
	/**
	 * @return the mappingResource
	 */
	public Resource getMappingResource() {
		return mappingResource;
	}

	/**
	 * @param mappingResource the mappingResource to set
	 */
	public void setMappingResource(Resource mappingResource) {
		this.mappingResource = mappingResource;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getProperties()
	 */
	public Properties getProperties() {
		return properties;
	}

	/**
	 * @param properties the properties to set
	 */
	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getDataSource()
	 */
	public SimpleDB getDataSource() {
		return dataSource;
	}

	/**
	 * @param dataSource the dataSource to set
	 */
	public void setDataSource(SimpleDB dataSource) {
		this.dataSource = dataSource;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getCache(java.lang.String)
	 */
	public Level2Cache getCache(String cacheName) {
		return cacheFactory.getCache(cacheName);
	}

	/**
	 * @return the cacheFactory
	 */
	public Level2CacheFactory getCacheFactory() {
		return cacheFactory;
	}

	/**
	 * @param cacheFactory the cacheFactory to set
	 */
	public void setCacheFactory(Level2CacheFactory cacheFactory) {
		this.cacheFactory = cacheFactory;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getMapping()
	 */
	public SimpleDBMapping getMapping() {
		return mapping;
	}

	/**
	 * @param mapping the mapping to set
	 */
	public void setMapping(SimpleDBMapping mapping) {
		this.mapping = mapping;
	}

	/**
	 * @return the numOfExecutorThreads
	 */
	public int getNumOfExecutorThreads() {
		return numOfExecutorThreads;
	}
	
	/**
	 * @param numOfExecutorThreads the numOfExecutorThreads to set
	 */
	public void setNumOfExecutorThreads(int numOfExecutorThreads) {
		this.numOfExecutorThreads = numOfExecutorThreads;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getQueryExecutor()
	 */
	public ExecutorService getQueryExecutor() {
		return queryExecutor;
	}

	/**
	 * @param queryExecutor the queryExecutor to set
	 */
	public void setQueryExecutor(ExecutorService queryExecutor) {
		this.queryExecutor = queryExecutor;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getManipulationExecutor()
	 */
	public ExecutorService getManipulationExecutor() {
		return manipulationExecutor;
	}

	/**
	 * @param manipulationExecutor the manipulationExecutor to set
	 */
	public void setManipulationExecutor(ExecutorService manipulationExecutor) {
		this.manipulationExecutor = manipulationExecutor;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionFactory#getMaxResults()
	 */
	public int getMaxResults() {
		return maxResults;
	}

	/**
	 * @param maxResults the maxResults to set
	 */
	public void setMaxResults(int maxResults) {
		this.maxResults = maxResults;
	}

	/**
	 * @return the mappingRulesResource
	 */
	public Resource getMappingRulesResource() {
		return mappingRulesResource;
	}

	/**
	 * @return the mappingResources
	 */
	public Resource[] getMappingResources() {
		return mappingResources;
	}

	/**
	 * @param mappingResources the mappingResources to set
	 */
	public void setMappingResources(Resource[] mappingResources) {
		this.mappingResources = mappingResources;
	}

	/**
	 * @param mappingRulesResource the mappingRulesResource to set
	 */
	public void setMappingRulesResource(Resource mappingRulesResource) {
		this.mappingRulesResource = mappingRulesResource;
	}

	/**
	 * @return the entityInterceptors
	 */
	public List<SimpleDBSessionListener> getEntityInterceptors() {
		return entityInterceptors;
	}

	/**
	 * @param entityInterceptors the entityInterceptors to set
	 */
	public void setEntityInterceptors(List<SimpleDBSessionListener> entityInterceptors) {
		this.entityInterceptors = entityInterceptors;
	}

}
